<template>
  <div class="dom-search-container">
    <div class="search-controls">
      <div class="search-input-group">
        <textarea
            id="cyDomSearchInput"
            v-model="searchText"
            placeholder="输入要查找的DOM元素文本内容、类名或ID..."
            @keyup.enter="searchElements"
        ></textarea>
      </div>

      <div class="search-options">
        <div class="option-group">
          <label class="option-label">搜索模式:</label>
          <select v-model="searchMode" class="option-select">
            <option value="text">文本内容</option>
            <option value="class">类名</option>
            <option value="id">ID</option>
            <option value="selector">CSS选择器</option>
            <option value="xpath">XPath</option>
          </select>
        </div>

        <div class="option-group">
          <label class="option-label">搜索范围:</label>
          <select v-model="searchScope" class="option-select">
            <option value="visible">仅可见元素</option>
            <option value="all">所有元素</option>
          </select>
        </div>

        <div class="option-group">
          <label class="option-label">匹配方式:</label>
          <select v-model="matchMode" class="option-select">
            <option value="contains">包含</option>
            <option value="exact">精确匹配</option>
            <option value="prefix">前缀匹配</option>
            <option value="suffix">后缀匹配</option>
            <option value="regex">正则表达式</option>
          </select>
        </div>

        <div class="option-group">
          <label class="option-label">最大结果:</label>
          <input
              type="number"
              v-model.number="maxResults"
              min="1"
              max="1000"
              class="option-input"
          >
        </div>
      </div>

      <div class="search-actions">
        <button
            class="search-button primary"
            @click="searchElements"
            :disabled="!searchText.trim()"
        >
          <i class="icon-search"></i> 搜索元素
        </button>
        <button
            class="search-button"
            @click="clearSearch"
        >
          <i class="icon-clear"></i> 清空
        </button>
      </div>
    </div>

    <div class="search-results" v-if="searchResults.length > 0">
      <div class="results-header">
        <span class="results-count">找到 {{ searchResults.length }} 个结果</span>
        <div class="results-actions">
          <button
              class="action-button"
              @click="highlightAll"
              :disabled="highlightedElements.length === searchResults.length"
          >
            <i class="icon-highlight"></i> 高亮全部
          </button>
          <button
              class="action-button"
              @click="unhighlightAll"
              :disabled="highlightedElements.length === 0"
          >
            <i class="icon-unhighlight"></i> 取消高亮
          </button>
          <button
              class="action-button danger"
              @click="removeAll"
          >
            <i class="icon-delete"></i> 删除全部
          </button>
        </div>
      </div>

      <div class="results-list">
        <div
            v-for="(result, index) in searchResults"
            :key="result.id"
            class="result-item"
            :class="{ highlighted: isHighlighted(result.element) }"
            @mouseenter="highlightElement(result.element, true)"
            @mouseleave="unhighlightElement(result.element)"
        >
          <div class="result-info">
            <div class="result-index">#{{ index + 1 }}</div>
            <div class="result-tag">{{ result.element.tagName.toLowerCase() }}</div>
            <div class="result-content" :title="result.content">
              {{ result.content }}
            </div>
            <div class="result-path" :title="result.path">
              {{ result.path }}
            </div>
          </div>

          <div class="result-actions">
            <button
                class="action-button small"
                @click="scrollToElement(result.element)"
                title="滚动到元素"
            >
              <i class="icon-scroll"></i>
            </button>
            <button
                class="action-button small"
                @click="inspectElement(result.element)"
                title="查看详情"
            >
              <i class="icon-inspect"></i>
            </button>
            <button
                class="action-button small"
                @click="editElement(result.element)"
                title="编辑元素"
            >
              <i class="icon-edit"></i>
            </button>
            <button
                class="action-button small danger"
                @click="removeElement(result.element, index)"
                title="删除元素"
            >
              <i class="icon-delete"></i>
            </button>
          </div>
        </div>
      </div>
    </div>

    <div class="no-results" v-else-if="hasSearched">
      <div class="no-results-message">
        没有找到匹配的元素
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onBeforeUnmount } from 'vue'

const props = defineProps({
  highlightColor: {
    type: String,
    default: '#ff1f71'
  }
})

const emit = defineEmits([
  'inspect',
  'edit',
  'scroll-to'
])

// 搜索状态
const searchText = ref('')
const searchMode = ref('text')
const searchScope = ref('visible')
const matchMode = ref('contains')
const maxResults = ref(50)
const hasSearched = ref(false)

// 搜索结果
const searchResults = ref([])
const highlightedElements = ref([])

// 生成唯一ID
const generateId = (() => {
  let counter = 0
  return () => `dom-search-result-${++counter}`
})()

// 搜索元素
const searchElements = () => {
  if (!searchText.value.trim()) return

  // 清除之前的结果和高亮
  clearHighlights()
  searchResults.value = []

  let elements = []
  const searchValue = searchText.value.trim()

  try {
    // 根据搜索模式获取元素
    switch (searchMode.value) {
      case 'text':
        elements = findElementsByText(searchValue)
        break
      case 'class':
        elements = findElementsByClass(searchValue)
        break
      case 'id':
        elements = findElementsById(searchValue)
        break
      case 'selector':
        elements = Array.from(document.querySelectorAll(searchValue))
        break
      case 'xpath':
        elements = evaluateXPath(searchValue)
        break
    }

    // 过滤不可见元素
    if (searchScope.value === 'visible') {
      elements = elements.filter(el => isElementVisible(el))
    }

    // 限制结果数量
    if (elements.length > maxResults.value) {
      elements = elements.slice(0, maxResults.value)
    }

    // 转换为结果格式
    searchResults.value = elements.map(el => ({
      id: generateId(),
      element: el,
      content: getElementContent(el),
      path: getElementPath(el)
    }))

    hasSearched.value = true
  } catch (error) {
    console.error('DOM搜索错误:', error)
    // 可以添加错误提示
  }
}

// 根据文本查找元素
const findElementsByText = (text) => {
  const walker = document.createTreeWalker(
      document.body,
      NodeFilter.SHOW_TEXT,
      null,
      false
  )

  const elements = []
  const regex = createMatchRegex(text)

  while (walker.nextNode()) {
    const node = walker.currentNode
    if (regex.test(node.textContent)) {
      let parent = node.parentElement
      if (parent && !elements.includes(parent)) {
        elements.push(parent)
      }
    }
  }

  return elements
}

// 根据类名查找元素
const findElementsByClass = (className) => {
  const regex = createMatchRegex(className)
  return Array.from(document.getElementsByTagName('*')).filter(el => {
    return el.className && regex.test(el.className)
  })
}

// 根据ID查找元素
const findElementsById = (id) => {
  const regex = createMatchRegex(id)
  return Array.from(document.getElementsByTagName('*')).filter(el => {
    return el.id && regex.test(el.id)
  })
}

// 评估XPath表达式
const evaluateXPath = (expression) => {
  const result = document.evaluate(
      expression,
      document,
      null,
      XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
      null
  )

  const elements = []
  for (let i = 0; i < result.snapshotLength; i++) {
    const node = result.snapshotItem(i)
    if (node.nodeType === Node.ELEMENT_NODE) {
      elements.push(node)
    }
  }

  return elements
}

// 创建匹配正则表达式
const createMatchRegex = (text) => {
  switch (matchMode.value) {
    case 'exact':
      return new RegExp(`^${escapeRegExp(text)}$`)
    case 'prefix':
      return new RegExp(`^${escapeRegExp(text)}`)
    case 'suffix':
      return new RegExp(`${escapeRegExp(text)}$`)
    case 'regex':
      try {
        return new RegExp(text)
      } catch (e) {
        return new RegExp(escapeRegExp(text))
      }
    default: // contains
      return new RegExp(escapeRegExp(text))
  }
}

// 转义正则表达式特殊字符
const escapeRegExp = (string) => {
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

// 获取元素内容
const getElementContent = (element) => {
  if (!element) return ''

  // 优先获取文本内容
  const text = element.textContent?.trim() || ''
  if (text) return text.length > 100 ? text.substring(0, 100) + '...' : text

  // 对于没有文本内容的元素，显示其他信息
  if (element.id) return `#${element.id}`
  if (element.className) return `.${element.className}`

  return element.tagName.toLowerCase()
}

// 获取元素路径
const getElementPath = (element) => {
  if (!element || !element.tagName) return ''

  const path = []
  let current = element

  while (current && current.tagName && current.tagName.toLowerCase() !== 'html') {
    let selector = current.tagName.toLowerCase()

    if (current.id) {
      selector += `#${current.id}`
      path.unshift(selector)
      break
    } else {
      let sibling = current
      let nth = 1

      while (sibling.previousElementSibling) {
        sibling = sibling.previousElementSibling
        nth++
      }

      if (current.className) {
        selector += `.${current.className.split(' ').join('.')}`
      }

      selector += `:nth-child(${nth})`
      path.unshift(selector)
    }

    current = current.parentNode
  }

  return path.join(' > ')
}

// 检查元素是否可见
const isElementVisible = (element) => {
  if (!element) return false

  // 检查样式可见性
  const style = window.getComputedStyle(element)
  if (style.display === 'none' || style.visibility === 'hidden') {
    return false
  }

  // 检查尺寸
  if (element.offsetWidth === 0 && element.offsetHeight === 0) {
    return false
  }

  // 检查是否在视口中
  const rect = element.getBoundingClientRect()
  return (
      rect.top < window.innerHeight &&
      rect.bottom > 0 &&
      rect.left < window.innerWidth &&
      rect.right > 0
  )
}

// 高亮元素
const highlightElement = (element, isTemporary = false) => {
  if (!element || !element.style) return

  element.style.setProperty('outline', `2px solid ${props.highlightColor}`, 'important')
  element.style.setProperty('outline-offset', '2px', 'important')
  element.style.setProperty('box-shadow', `0 0 10px ${props.highlightColor}`, 'important')

  if (!isTemporary && !highlightedElements.value.includes(element)) {
    highlightedElements.value.push(element)
  }
}

// 取消高亮元素
const unhighlightElement = (element) => {
  if (!element || !element.style) return

  element.style.removeProperty('outline')
  element.style.removeProperty('outline-offset')
  element.style.removeProperty('box-shadow')

  highlightedElements.value = highlightedElements.value.filter(el => el !== element)
}

// 检查元素是否已高亮
const isHighlighted = (element) => {
  return highlightedElements.value.includes(element)
}

// 高亮所有结果
const highlightAll = () => {
  searchResults.value.forEach(result => {
    highlightElement(result.element)
  })
}

// 取消所有高亮
const unhighlightAll = () => {
  highlightedElements.value.forEach(el => {
    unhighlightElement(el)
  })
  highlightedElements.value = []
}

// 清除所有高亮
const clearHighlights = () => {
  unhighlightAll()
}

// 滚动到元素
const scrollToElement = (element) => {
  if (element && element.scrollIntoView) {
    element.scrollIntoView({
      behavior: 'smooth',
      block: 'center'
    })
    emit('scroll-to', element)
  }
}

// 查看元素详情
const inspectElement = (element) => {
  emit('inspect', element)
}

// 编辑元素
const editElement = (element) => {
  emit('edit', element)
}

// 删除单个元素
const removeElement = (element, index) => {
  if (element && element.parentNode) {
    element.parentNode.removeChild(element)
    searchResults.value.splice(index, 1)
    unhighlightElement(element)
  }
}

// 删除所有结果
const removeAll = () => {
  searchResults.value.forEach(result => {
    if (result.element && result.element.parentNode) {
      result.element.parentNode.removeChild(result.element)
    }
  })
  searchResults.value = []
  highlightedElements.value = []
}

// 清空搜索
const clearSearch = () => {
  searchText.value = ''
  searchResults.value = []
  highlightedElements.value = []
  hasSearched.value = false
}

// 组件卸载前清除高亮
onBeforeUnmount(() => {
  clearHighlights()
})
</script>

<style scoped>
/* DomSearch.vue 的完整样式部分 */
.dom-search-container {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 15px;
  background: rgba(10, 20, 30, 0.85);
  border-radius: 12px;
  box-shadow: 0 0 15px rgba(0, 255, 135, 0.3);
  border: 1px solid rgba(0, 255, 135, 0.5);
  font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
  color: rgba(255, 255, 255, 0.9);
}

.search-controls {
  display: flex;
  flex-direction: column;
  gap: 15px;
  margin-bottom: 20px;
}

.search-input-group {
  flex: 1;
  position: relative;
}

#cyDomSearchInput {
  width: 100%;
  min-height: 80px;
  padding: 12px;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 6px;
  color: rgba(255, 255, 255, 0.9);
  font-family: 'Fira Code', monospace;
  resize: vertical;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

#cyDomSearchInput:focus {
  outline: none;
  border-color: rgba(0, 255, 135, 0.5);
  box-shadow: 0 0 0 2px rgba(0, 255, 135, 0.2);
}

.search-options {
  display: flex;
  flex-wrap: wrap;
  gap: 15px;
  padding: 10px;
  background: rgba(0, 0, 0, 0.3);
  border-radius: 6px;
}

.option-group {
  display: flex;
  align-items: center;
  gap: 8px;
}

.option-label {
  color: rgba(255, 255, 255, 0.7);
  font-size: 14px;
  white-space: nowrap;
}

.option-select, .option-input {
  padding: 6px 10px;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 4px;
  color: rgba(255, 255, 255, 0.9);
  font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
}

.option-select {
  min-width: 120px;
}

.option-input {
  width: 60px;
}

.search-actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
}

.search-button {
  padding: 8px 16px;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
  font-weight: bold;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  display: flex;
  align-items: center;
  gap: 8px;
}

.search-button:hover {
  transform: translateY(-2px);
  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}

.search-button.primary {
  background: linear-gradient(135deg, #00ff87 0%, #60efff 100%);
  color: #111;
}

.search-button.secondary {
  background: linear-gradient(135deg, #ff1f71 0%, #a020f0 100%);
  color: white;
}

.search-button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.search-results {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.3);
  border-radius: 6px;
}

.results-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 15px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}

.results-count {
  font-size: 14px;
  color: rgba(255, 255, 255, 0.7);
}

.results-actions {
  display: flex;
  gap: 8px;
}

.action-button {
  padding: 6px 12px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 4px;
  color: rgba(255, 255, 255, 0.9);
  font-size: 12px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.action-button:hover {
  background: rgba(255, 255, 255, 0.1);
}

.action-button.danger {
  color: #ff5555;
  border-color: rgba(255, 85, 85, 0.3);
}

.action-button.danger:hover {
  background: rgba(255, 85, 85, 0.1);
}

.action-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.action-button.small {
  padding: 4px 8px;
  font-size: 11px;
}

.results-list {
  flex: 1;
  overflow-y: auto;
  padding: 5px;
}

.result-item {
  padding: 10px;
  margin: 5px;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 4px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.result-item:hover {
  background: rgba(255, 255, 255, 0.1);
}

.result-item.highlighted {
  background: rgba(255, 31, 113, 0.1);
}

.result-info {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  overflow: hidden;
}

.result-index {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.7);
  min-width: 30px;
}

.result-tag {
  font-size: 12px;
  background: rgba(0, 255, 135, 0.2);
  color: #00ff87;
  padding: 2px 6px;
  border-radius: 4px;
  white-space: nowrap;
}

.result-content {
  flex: 1;
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.result-path {
  font-size: 11px;
  color: rgba(255, 255, 255, 0.7);
  font-family: 'Fira Code', monospace;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 200px;
}

.result-actions {
  display: flex;
  gap: 5px;
}

.no-results {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  color: rgba(255, 255, 255, 0.7);
  font-size: 14px;
  padding: 20px;
}

/* 图标样式 */
.icon-search,
.icon-clear,
.icon-highlight,
.icon-unhighlight,
.icon-delete,
.icon-scroll,
.icon-inspect,
.icon-edit {
  display: inline-block;
  width: 14px;
  height: 14px;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  filter: brightness(0.8);
}

.icon-search {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2300ff87'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z' /%3E%3C/svg%3E");
}

.icon-clear {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%23ffffff'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16' /%3E%3C/svg%3E");
}

.icon-highlight {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%23ffffff'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z' /%3E%3C/svg%3E");
}

.icon-unhighlight {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%23ffffff'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21' /%3E%3C/svg%3E");
}

.icon-delete {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%23ff5555'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16' /%3E%3C/svg%3E");
}

.icon-scroll {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%23ffffff'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 14l-7 7m0 0l-7-7m7 7V3' /%3E%3C/svg%3E");
}

.icon-inspect {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2300bfff'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M15 12a3 3 0 11-6 0 3 3 0 016 0z' /%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z' /%3E%3C/svg%3E");
}

.icon-edit {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%23ffd700'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z' /%3E%3C/svg%3E");
}

/* 响应式设计 */
@media (max-width: 768px) {
  .dom-search-container {
    padding: 10px;
  }

  .search-options {
    flex-direction: column;
    gap: 10px;
  }

  .option-group {
    flex-direction: column;
    align-items: flex-start;
  }

  .result-info {
    flex-direction: column;
    align-items: flex-start;
    gap: 5px;
  }

  .result-path {
    max-width: 100%;
  }

  .results-actions {
    flex-wrap: wrap;
  }
}

/* 滚动条样式 */
.results-list::-webkit-scrollbar {
  width: 8px;
}

.results-list::-webkit-scrollbar-track {
  background: rgba(255, 255, 255, 0.1);
  border-radius: 4px;
}

.results-list::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.3);
  border-radius: 4px;
}

.results-list::-webkit-scrollbar-thumb:hover {
  background: rgba(255, 255, 255, 0.4);
}
</style>
